package com.mypack.dao.helper;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

/**
 * Jpa helper class
 * help to create entity manager in thread context
 * and manage transaction in thread context
 * auto commit and rollback
 * <p>client can use template method like this if you a result
 * needn't a transaction<br>
 * <code>
 * JpaHelper.beginTtransation();
 * try {
 *	 // call dao method ...
 * } finally {
 *   JpaHelper.endTransaction();
 * }
 * </code>
 * or you can call like this
 * <code>
 *	JpaHelper.executeFind(new JpaFindCallback<Config>() {
 *
 *		@Override
 *		public Config doJpaFind() throws Exception {
 *			
 *			// call dao method and return a result
 *		}
 *		
 *	});
 * </code>
 * <p>if you need a transaction, you should call like this
 * <code>
 * JpaHelper.doTransation(new JpaCallback() {
 * 
 *		@Override
 *		public void doInJpa() throws Exception {
 *			// call dao method in a transaction
 *		}
 *		
 *	});
 * </code>
 */
public final class JpaHelper {
	private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("transactions-optional");
	private static final ThreadLocal<EntityManager> emholder = new ThreadLocal<EntityManager>();
	private static final Logger logger = Logger.getLogger(JpaHelper.class.getName());
	
	/**
	 * get the entity manager in the context
	 */
	public static EntityManager getEntityManager() {
		EntityManager em = emholder.get();
		
		if (em == null) {
			em = emf.createEntityManager();
			logger.info("new EntityManager = " + em);
			emholder.set(em);
		}
		logger.info("EntityManager = " + em);
		
		return em;
	}
	
	/**
	 * jpa transation template
	 * auto commit,rollback jpa transation
	 * @param callback JpaCallback
	 */
	public static <T> T executeFind(JpaFindCallback<T> callback) {
		beginTtransation();
		try {
			return callback.doJpaFind();
		} catch (Exception e) {
			setRollbackOnly();
		} finally {
			endTransaction();
		}
		return null;
	}
	
	/**
	 * jpa transation template
	 * auto commit,rollback jpa transation
	 * @param callback JpaCallback
	 */
	public static void doTransation(JpaCallback callback) {
		beginTtransation();
		try {
			callback.doInJpa();
		} catch (Exception e) {
			setRollbackOnly();
			logger.log(Level.WARNING, "something happened, so rollback tx.", e);
		} finally {
			endTransaction();
		}
	}
	
	/**
	 * Mark the current transaction so that the only possible outcome
	 * of the transaction is for the transaction to be rolled back.
	 */
	public static void setRollbackOnly() {
		getEntityManager().getTransaction().setRollbackOnly();
	}
	
	/**
	 * begin a transation
	 */
	public static void beginTtransation() {
		EntityTransaction tx = getEntityManager().getTransaction();
		if (tx.isActive()) {
			logger.info("an active transaction already begin, do nothing");
		} else {
			logger.info("begin a new transaction , transaction is " + tx);
			tx.begin();
		}
	}
	
	/**
	 * commit the transation in the thread context
	 */
	public static void commit() {
		EntityTransaction tx = getEntityManager().getTransaction();
		logger.info("commit transaction is " + tx);
		if (tx.isActive()) {
			tx.commit();
		}
		else {
			logger.info("the transaction is not active, do nothing");
		}
	}
	
	/**
	 * rollback the transation in the thread context
	 */
	public static void rollback() {
		EntityTransaction tx = getEntityManager().getTransaction();
		logger.info("rollback transaction is " + tx);
		if (tx.isActive()) {
			tx.rollback();
		}
		else {
			logger.info("the transaction is not active, do nothing");
		}
	}
	
	/**
	 * close the entity manager in the thread context
	 * throw an runtime exception if entity manager
	 * not created in the context.
	 */
	public static void endTransaction() {
		EntityManager em = emholder.get();
		logger.info("JpaHelper.endTransaction :: EntityManager = " + em);
		//Assert.notNull(em, "entity manager in context must not be null");
		if (em == null || !em.isOpen()) {
			logger.info("entityManager is null or not opened, do nothing");
			emholder.remove();
			return;
		}
		
		EntityTransaction tx = em.getTransaction();
		logger.info("transaction is " + tx);
		
		if (tx.isActive()) {
			logger.info("JpaHelper.endTransaction transation is active");
			if (tx.getRollbackOnly()) {
				tx.rollback();
				logger.info("JpaHelper.endTransaction rollback transation");
			}
			else {
				tx.commit();
				logger.info("JpaHelper.endTransaction commit transation");
			}
		}
		em.close();
		logger.info("entity manager closed.");
		emholder.remove();
	}
}
